/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.extended.app;

import org.proteusframework.core.api.IPluginRegistrar;
import org.proteusframework.core.api.IProteusApplication;
import org.proteusframework.core.api.IRuntimeEngine;
import org.proteusframework.core.api.RuntimeEnvironment;
import org.proteusframework.core.api.model.*;
import org.proteusframework.core.base.*;
import org.proteusframework.extended.app.messages.PersonPOJO;
import org.proteusframework.platformservice.AbstractExtendedRuntimeEngine;
import org.proteusframework.platformservice.ExtendedProteusApplication;
import org.proteusframework.platformservice.IExtendedPlatformDelegate;
import org.proteusframework.platformservice.events.IEventConsumer;
import org.proteusframework.platformservice.persistence.api.*;
import org.proteusframework.platformservice.persistence.api.messagebean.IMessageBeanDescriptor;
import org.proteusframework.platformservice.persistence.messagebean.DataType;
import org.proteusframework.platformservice.persistence.messagebean.MessageBeanBuilder;
import org.proteusframework.platformservice.persistence.messagebean.UnsupportedMessageBeanException;
import org.proteusframework.platformservice.persistence.project.NoSuchProjectException;
import org.proteusframework.platformservice.persistence.project.ProjectDescriptor;
import org.proteusframework.platformservice.persistence.vfs.FilePath;
import org.proteusframework.platformservice.theme.IThemeService;
import org.proteusframework.platformservice.theme.menu.*;

import javax.imageio.ImageIO;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

public class SampleExtendedApplication extends AbstractExtendedRuntimeEngine
{
    private static final Logger logger = Logger.getLogger(SampleExtendedApplication.class.getCanonicalName());
    private static final String ORG_PROTEUSFRAMEWORK_EXTENDED_APP_MESSAGES = "org.proteusframework.extended.app.messages";
    private static final String PERSON_POJO = "PersonPOJO";
    private static IProteusApplication myApplication;

    /*
    Proteus was designed to run on either Java SE or Android. As a result of this, the class
    <code>java.awt.BufferedImage</code> is only available on Java SE; Android uses Bitmap. Therefore, the framework
    had to abstract out the graphical representation of glyphs in order to fulfill its requirement of running on
    either Java SE or Android. This class uses Java SE classes and operations to render a glyph as a
    <code>java.awt.BufferedImage</code>.
     */
    private static final IGlyphRenderer GLYPH_RENDERER = new IGlyphRenderer()
    {
        @SuppressWarnings({"unchecked"})
        @Override
        public <T> T renderGlyph(IGlyph glyph, Class<T> targetRenderedType)
        {
            T targetFormat = null;

            try
            {
                targetFormat = (T) ImageIO.read(glyph.getGlyphURL());
            } catch (IOException e)
            {
                logger.warning("Unable to locate " + glyph.getGlyphURL() + ": " + e.getMessage());
            }

            return targetFormat;
        }
    };


    public static void main(String[] args)
    {
        //
        // Let's crank up the logging level to FINEST so we can see what's going on behind the scenes
        //

        /*
        Handler[] handlers = Logger.getLogger("").getHandlers();
        for (Handler handler : handlers)
        {
            handler.setLevel(Level.FINEST);
        }

        LogManager.getLogManager().getLogger("").setLevel(Level.FINEST);
        */


        //
        // Our sample application is content using the standard Proteus application architecture.
        //
        myApplication = new ExtendedProteusApplication();


        //
        // Generally, one asks the application for a configuration loader, referring to the application's documentation
        // for detailed information on the format expected by the configuration loader.
        //
        // Our sample application could follow this pattern, using the StandardConfigurationLoader that
        // expects the inputStream to point to a .properties file
        //

        /*
        IConfigurationLoader configLoader = myApplication.createConfigurationLoader();
        File file = new File("/path/to/some.properties");
        InputStream inputStream = new FileInputStream(file);
        IRuntimeConfiguration myRuntimeConfiguration = configLoader.load(inputStream);
        IRuntimeEngine myRuntimeEngine = new SampleApplication(myRuntimeConfiguration);
        */

        //
        // The runtime engine's constructor MUST NOT actually initialize itself because the IPlatformDelegate has yet to
        // be injected into the runtime engine. Instead, configuration and preparing for execution should happen within
        // an override on MyRuntimeEngine.onDelegateInjected()
        //
        IRuntimeEngine myRuntimeEngine = new SampleExtendedApplication();

        //
        // The runtime engine, uninitialized, is handed to the application delegate. The application delegate
        // has the responsibility of injecting itself into the runtime engine, indirectly triggering
        // onDelegateInjected() on the myRuntimeEngine-- allowing the engine to initialize itself, but it MUST NOT
        // actually run yet.
        //
        myApplication.setRuntimeEngine(myRuntimeEngine);


        //
        // Finally, with the runtime engine injected into the Proteus Application, the application is ready to be
        // launched.
        //
        myApplication.startApplication();
    }

    public SampleExtendedApplication()
    {
        super(new Namespace("org.proteusframework.extended.app", "SampleExtendedApp"), RuntimeEnvironment.Desktop, GLYPH_RENDERER);
    }

    @Override
    protected void registerMessageBeans(IMessageBeanManager messageBeanManager)
    {
        IMessageBeanDescriptor personDescriptor = new MessageBeanBuilder(ORG_PROTEUSFRAMEWORK_EXTENDED_APP_MESSAGES,
                PERSON_POJO,
                "Person")
                .mapProperty("id", DataType.IdentityType, true)
                .mapProperty("firstName", DataType.StringType)
                .mapProperty("lastName", DataType.StringType)
                .build();

        try
        {
            messageBeanManager.queueMessageBeanRegistration(personDescriptor);
        } catch (UnsupportedMessageBeanException e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void run()
    {
        logger.info("The application would formally launch here in the run() method");

        //
        // Any engine configuration or initialization would take place here - but the application has not formally
        // launched yet.  The application formally launches when the run() method is invoked. This two-step process
        // gives the application the ability to verify that the Proteus Factory Registrar actually has found all
        // of the mandatory Plugin Registrar instances that the application requires to run. If not, it is considered a
        // Proteus Framework best practice for the application to gracefully exit here instead of inside of the run()
        // method.
        //

        try
        {
            INamespace SAMPLEQN = new Namespace("sample", "RegistrarFactory");
            IPluginRegistrar pluginRegistrar = delegate.getPluginRegistrar(SAMPLEQN);

            Collection<IVersion> versions = pluginRegistrar.versions();

            for (IVersion version : versions)
            {
                logger.info("Installed Version: " + version.toString());
            }


            /////////////
            //
            // Get Version 1 Interface, default plugin provider
            //
            /////////////

            MyExpectedVersion1Interface example1 = pluginRegistrar.createPlugin(new Version(1), MyExpectedVersion1Interface.class);

            logger.info("Example 1: " + example1.doCalculation(23));


            /////////////
            //
            // Get Version 1 Registrar, Version 1 Interface
            //
            /////////////

            IVersionDescriptor versionDescriptorA =
                    new VersionDescriptor(new Namespace("sample", "SamplePluginV1Registrar"), new Version(1, 0));


            MyExpectedVersion1Interface example2 = pluginRegistrar.createPlugin(versionDescriptorA, MyExpectedVersion1Interface.class);
            logger.info("Example 2: " + example2.doCalculation(23));

            /////////////
            //
            // Get Version 2 Registrar, Version 1 Interface
            // Registrar offers a backwards compatible implementation, albeit with a different calculation formula
            //
            /////////////

            IVersionDescriptor versionDescriptorB =
                    new VersionDescriptor(new Namespace("sample", "SamplePluginV2Registrar"), new Version(1));


            MyExpectedVersion1Interface example3 = pluginRegistrar.createPlugin(versionDescriptorB, MyExpectedVersion1Interface.class);
            logger.info("Example 3: " + example3.doCalculation(23));

            /////////////
            //
            // Get Version 2 Registrar, Version 2 Interface
            //
            /////////////

            IVersionDescriptor versionDescriptorC =
                    new VersionDescriptor(new Namespace("sample", "SamplePluginV2Registrar"), new Version(2, 0));

            MyExpectedVersion2Interface example4 = pluginRegistrar.createPlugin(versionDescriptorC, MyExpectedVersion2Interface.class);
            logger.info("Example 4: " + example4.doCalculation(23));
            logger.info("Even Check: " + example4.isEven(23));

        } catch (NoSuchNamespaceException e)
        {
            e.printStackTrace();
        } catch (NoSuchVersionException e)
        {
            e.printStackTrace();
        }


        final IThemeService theme = delegate.adapt(IExtendedPlatformDelegate.class).getThemeService();

        theme.createFrame();

        IMenuBlock menuBlock = new MenuBlock("FileMenu")
                .setProperty(IMenuBlock.KEY_LABEL, "File")
                .setProperty(IMenuBlock.KEY_MNEMONIC, "F")
                .setProperty(IMenuBlock.KEY_ICON, "/some/resource/path/to/icon.png");

        IMenuBlockItem exitApp = new MenuBlockItem("ExitApp")
                .setProperty(IMenuBlockItem.KEY_LABEL, "Exit")
                .setProperty(IMenuBlockItem.KEY_MNEMONIC, "x");


        exitApp.addEventListener(new IEventConsumer<MenuEvent>()
        {
            @Override
            public void handleEvent(MenuEvent event)
            {
                myApplication.stopApplication();
                System.exit(0);
            }
        });


        IMenuBlockItem newProject = new MenuBlockItem("NewProject")
                .setProperty(IMenuBlockItem.KEY_LABEL, "New Project...")
                .setProperty(IMenuBlockItem.KEY_MNEMONIC, "P");

        newProject.addEventListener(new IEventConsumer<MenuEvent>()
        {
            @Override
            public void handleEvent(MenuEvent event)
            {
                IPersistenceService persistence = delegate.adapt(IExtendedPlatformDelegate.class).getPersistenceService();


                // This demo leaks the BasicPersistenceService impl, but it's almost 0200 and I just want to see this work!

                INamespace namespace = new Namespace(
                        delegate.adapt(IExtendedPlatformDelegate.class).getAppConfigService().getDefaultProjectsDirectory(),
                        "My New Project 2");

                IProjectDescriptor descriptor = new ProjectDescriptor(namespace);

                try
                {
                    persistence.create(descriptor);
                } catch (org.proteusframework.platformservice.persistence.project.ProjectInitializationException e)
                {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }

                theme.showStatusMessage("New project created!");


                IVFSManager vfsManager = persistence.narrow(IVFSManager.class);

                FilePath fp = new FilePath("docs/snoopy.txt");
                IVFSFile vfsFile = vfsManager.createFile(fp);

                FileWriter writer = null;
                try
                {
                    writer = new FileWriter(vfsFile.getFile());
                    writer.write("The quick brown fox");
                } catch (IOException e)
                {
                    e.printStackTrace();
                } finally
                {

                    if (null != writer)
                    {
                        try
                        {
                            writer.flush();
                        } catch (IOException e)
                        {
                            e.printStackTrace();
                        }

                        try
                        {
                            writer.close();
                        } catch (IOException e)
                        {
                            e.printStackTrace();
                        }

                    }
                }

                theme.showStatusMessage("File written to VFS!");

                List<IVFSFile> files = vfsManager.getFiles(new FilePath("docs"));

                theme.showStatusMessage("Found a total of " + files.size() + " files.");

                IVFSFile file = vfsManager.getFile(new FilePath("docs/snoopy.txt"));

                theme.showStatusMessage("Found actual file at " + file.getAbsolutePath());

                vfsManager.deleteFile(file);

                theme.showStatusMessage("File deleted from VFS!");

                INamespace targetNamespace =
                        new Namespace(ORG_PROTEUSFRAMEWORK_EXTENDED_APP_MESSAGES, PERSON_POJO);

                IMessageBeanDescriptor msgDescriptor = persistence.getMessageBeanDescriptor(targetNamespace);


                if (null != msgDescriptor)
                {
                    theme.showStatusMessage(msgDescriptor.getCanonicalName() + " was located in the database!");
                } else
                {
                    theme.showStatusMessage("Unable to locate PersonPOJO in the database!");
                }

                PersonPOJO tacomaFour = new PersonPOJO();
                tacomaFour.setFirstName("Tacoma");
                tacomaFour.setLastName("Four");

                long key1 = persistence.saveMessage(PersonPOJO.class, tacomaFour);
                logger.info("Key assigned to Tacoma Four saved message: " + key1);

                PersonPOJO ghostRider = new PersonPOJO();
                ghostRider.setFirstName("Ghost");
                ghostRider.setLastName("Rider");

                long key2 = persistence.saveMessage(PersonPOJO.class, ghostRider);
                logger.info("Key assigned to Ghost Rider saved message: " + key2);

                try
                {
                    persistence.saveAs(new ProjectDescriptor(new Namespace(
                            delegate.adapt(IExtendedPlatformDelegate.class).getAppConfigService().getDefaultProjectsDirectory(),
                            "My Project Copy")));
                } catch (org.proteusframework.platformservice.persistence.project.ProjectInitializationException e)
                {
                    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
                }

                theme.showStatusMessage("Save As project completed!");

                persistence.close();

                theme.showStatusMessage("Project now closed!");

                try
                {
                    persistence.delete(descriptor);
                } catch (NoSuchProjectException e)
                {
                    e.printStackTrace();
                }
            }
        });

        theme.createMenuBlock(menuBlock, newProject, exitApp);

    }
}
